package com.linkoog.devtools.code_generator.template.render;

import com.linkoog.devtools.code_generator.KeyConstants;
import com.linkoog.devtools.code_generator.database.domain.TableInfo;
import com.linkoog.devtools.code_generator.common.MessageNotification;
import com.linkoog.devtools.code_generator.setting.persistent.CodeGenDevToolsPersistent;
import com.linkoog.devtools.code_generator.template.model.NodeData;
import com.linkoog.devtools.code_generator.template.model.node.FileNodeData;
import com.linkoog.devtools.code_generator.template.model.node.DirNodeData;
import com.linkoog.devtools.code_generator.template.model.node.TemplateNodeData;
import com.linkoog.devtools.code_generator.template.render.engine.TemplateEngine;
import com.linkoog.devtools.code_generator.template.render.engine.TemplateEngineFactory;
import com.linkoog.devtools.code_generator.util.PsiDirectoryUtil;
import com.linkoog.devtools.code_generator.util.PsiFileUtil;
import com.linkoog.devtools.code_generator.util.StringUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task.Backgroundable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiFileFactory;
import com.intellij.psi.PsiManager;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;

import java.text.SimpleDateFormat;
import java.util.*;

public class TemplateRender {

    @NotNull
    private final Project project;

    private final PsiFileFactory psiFileFactory;



    public TemplateRender(@NotNull final Project project) {
        this.project = project;
        this.psiFileFactory = PsiFileFactory.getInstance(project);
    }

    public static TemplateRender getInstance(@NotNull final Project project) {
        return ServiceManager.getService(project, TemplateRender.class);
    }

    public void generateCode(@NotNull VirtualFile baseDirectoryVF, @NotNull List<TableInfo> tableInfoList, @NotNull TemplateNodeData templateNodeData, @NotNull Map<String,Object> dataMap){
        PsiDirectory basePsiDirectory = PsiManager.getInstance(project).findDirectory(baseDirectoryVF);
        if (basePsiDirectory != null){
            generateCode(basePsiDirectory, tableInfoList, templateNodeData, dataMap);
        }
    }

    public void generateCode(@NotNull PsiDirectory basePsiDirectory,@NotNull List<TableInfo> tableInfoList, @NotNull TemplateNodeData templateNodeData, @NotNull Map<String,Object> dataMap){

        ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> ProgressManager.getInstance().run(new Backgroundable(project, "Generate Code ...") {
            @Override
            public void run(@NotNull ProgressIndicator indicator) {
                indicator.setFraction(0D);
                indicator.setText("prepare to generate");

                putDefaultData(dataMap);
                TemplateEngine templateEngine = TemplateEngineFactory.getTemplateEngine(templateNodeData.getTemplateEngineType());
                Map<String, NodeData> flatMap = flatMap(templateNodeData);

                indicator.setIndeterminate(false);
                indicator.setText("start to generate code ...");


                for (TableInfo tableInfo : tableInfoList) {
                    String entityName = tableInfo.getJavaEntityName();
                    if (StringUtils.isBlank(tableInfo.getJavaEntityName())){
                        entityName = StringUtil.textToCamelCase(tableInfo.getTableName(),true);
                    }
                    dataMap.put(KeyConstants.ENTITY_NAME, entityName);
                    dataMap.put(KeyConstants.TABLE, tableInfo);

                    List<PsiFileUtil.CreateFileParam> createFileParams = new ArrayList<>();
                    for (String path : flatMap.keySet()){
                        indicator.setFraction(1.0/flatMap.keySet().size());
                        NodeData nodeData = flatMap.get(path);

                        if (nodeData instanceof DirNodeData){
                            DirNodeData dirNodeData = (DirNodeData)nodeData;
                            try {
                                String directoryPath = templateEngine.render(path, dataMap);
                                indicator.setText(String.format("create directory [%s] ...", directoryPath));
                                PsiDirectory psiDirectory = WriteCommandAction.runWriteCommandAction(project, (Computable<PsiDirectory>) () -> PsiDirectoryUtil.createSubDirectory(basePsiDirectory, directoryPath));
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        } else if (nodeData instanceof FileNodeData){
                            FileNodeData fileNodeData = (FileNodeData)nodeData;
                            String rawFileName = fileNodeData.getName();
                            String parentPath = path.substring(0, path.length() - rawFileName.length());

                            try {
                                String fileName = templateEngine.render(rawFileName, dataMap);
                                String context = templateEngine.render(fileNodeData.getTemplate(), dataMap);
                                indicator.setText(String.format("generator template %s ...", fileName));
                                createFileParams.add(new PsiFileUtil.CreateFileParam(parentPath, fileName, context));
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }

                    try {
                        PsiFileUtil.createFile(project, basePsiDirectory, createFileParams);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

                // Finished
                indicator.setFraction(1D);
                indicator.setText("finished");

                MessageNotification.infoNotify("Successfully generated code for you");
            }
        })));


        ApplicationManager.getApplication().invokeLater(() ->ApplicationManager.getApplication().runReadAction(() -> {
        }));
    }

    private void putDefaultData(Map<String, Object> dataMap){
        dataMap.put(KeyConstants.DATE, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        dataMap.put(KeyConstants.REMARK, "DevTools-代码生成器");
        dataMap.put(KeyConstants.COMMON_COLUMNS, CodeGenDevToolsPersistent.getInstance().getState().getCommonColumnNameList());
    }

    private Map<String,NodeData> flatMap(TemplateNodeData templateNodeData){
        Map<String,NodeData> treeData = new LinkedHashMap<>();
        List<NodeData> nodeDataList =  templateNodeData.getChildNodeList();
        for (NodeData nodeData : nodeDataList) {
            flatMap("", nodeData, treeData);
        }
        return treeData;
    }

    private void flatMap(String parentPath, NodeData nodeData, Map<String,NodeData> treeData){
        if (nodeData instanceof DirNodeData){
            DirNodeData dirNodeData = (DirNodeData)nodeData;
            String currentPath = parentPath + dirNodeData.getName() + "/";
            treeData.put(currentPath, dirNodeData);

            List<NodeData> nodeDataList =  dirNodeData.getChildNodeList();
            for (NodeData data : nodeDataList) {
                flatMap(currentPath, data, treeData);
            }
        } else if (nodeData instanceof FileNodeData){
            FileNodeData fileNodeData = (FileNodeData)nodeData;
            treeData.put(parentPath + fileNodeData.getName(), fileNodeData);
        }
    }

}
